from django.shortcuts import render, HttpResponse, redirect
from django.conf import settings
from django.views.decorators.http import require_http_methods
from django.http import FileResponse, JsonResponse
from .file_handlers import list_files
from .models import ShortURL
import os
from django.views.decorators.csrf import csrf_exempt
from django.core.cache import cache

from django.http import HttpRequest
from django.http.response import JsonResponse
from urllib.parse import quote

@csrf_exempt
@require_http_methods(["POST"])
def shorten_url(request: HttpRequest) -> JsonResponse:
    """
    生成短链接API
    
    Args:
        request: HttpRequest对象，可以包含:
            - POST数据: {'url': '原始URL'}
            - GET参数: ?url=原始URL
            - JSON body: {'url': '原始URL'}
            
    Returns:
        JsonResponse: 包含以下字段的JSON响应:
            - shortUrl: 完整的短链接URL
            - shortCode: 短链接代码
            或错误信息:
            - error: 错误描述
            
    Raises:
        Http400: 当URL参数缺失或无效时
        Http405: 当请求方法不支持时
        Http500: 当服务器内部错误时
        
    Example:
        >>> 请求: POST /api/shorten/ {'url': 'https://example.com'}
        <<< 响应: {'shortUrl': 'http://short.ly/abc123', 'shortCode': 'abc123'}
    """
    if request.method == 'POST':
        try:
            # 从POST数据或JSON中获取URL
            original_url = request.POST.get('url') or request.GET.get('url')
            if not original_url:
                # 尝试从JSON body中获取
                import json
                try:
                    data = json.loads(request.body)
                    original_url = data.get('url')
                except:
                    pass
                
                if not original_url:
                    return JsonResponse({'error': 'URL参数缺失'}, status=400)

            # 验证URL格式
            if not original_url.startswith(('http://', 'https://')):
                original_url = 'http://' + original_url

            # 创建短链接
            short_url = ShortURL.create_short_url(original_url)
            return JsonResponse({
                'shortUrl': short_url.get_short_url(request),
                'shortCode': short_url.short_code
            })
        except Exception as e:
            import logging
            logging.getLogger(__name__).error(f"生成短链接失败: {str(e)}", exc_info=True)
            return JsonResponse({'error': '服务器内部错误'}, status=500)
    return JsonResponse({'error': '只支持POST请求'}, status=405)

from django.http import HttpRequest, HttpResponse, HttpResponseRedirect
from django.shortcuts import redirect

def redirect_short_url(request: HttpRequest, short_code: str) -> HttpResponse:
    """
    短链接重定向视图
    
    Args:
        request: HttpRequest对象
        short_code: 短链接代码(字符串格式)
            
    Returns:
        HttpResponseRedirect: 重定向到原始URL
        HttpResponse: 错误响应(404)
        
    Raises:
        Http404: 当短链接不存在时
        
    Example:
        >>> 请求: GET /s/abc123
        <<< 响应: 重定向到原始URL或404错误
    """
    try:
        short_url = ShortURL.objects.get(short_code=short_code)
        return redirect(short_url.original_url)
    except ShortURL.DoesNotExist:
        return HttpResponse('短链接不存在', status=404)
from django.core.paginator import Paginator

from django.http import HttpRequest, HttpResponse
from typing import Optional

def index(request: HttpRequest, file_path: Optional[str] = None) -> HttpResponse:
    """
    文件浏览视图 - 显示指定路径下的文件和目录
    
    Args:
        request: HttpRequest对象，包含查询参数:
            - q: 搜索关键词(可选)
            - recursive: 是否递归搜索(true/false, 默认为true)
            - page: 分页页码(可选)
            - limit: 每页显示数量(可选)
            - sort: 排序字段(name/mtime, 默认为name)
            - order: 排序顺序(asc/desc, 默认为asc)
        file_path: 可选路径参数(相对FILE_ROOT的路径)
            
    Returns:
        HttpResponse: 渲染的文件列表页面，包含以下上下文:
            - files: 文件/目录列表
            - page_obj: 分页对象
            - current_path: 当前路径
            - root_path: 根路径
            - breadcrumbs: 面包屑导航
            - sort: 当前排序字段
            - order: 当前排序顺序
        
    Raises:
        PermissionError: 当无法访问指定路径时
        Http400: 当路径越界时
        
    Example:
        >>> 请求: GET /browse/?q=test&recursive=true&page=2
        <<< 响应: 渲染的文件列表页面
    """
    # 生成缓存键
    cache_key = f"file_list:{file_path or 'root'}:{request.GET.urlencode()}"
    
    # 尝试从缓存获取
    cached_result = cache.get(cache_key)
    if cached_result:
        return cached_result
    # 安全路径处理
    base_path = settings.FILE_ROOT
    
    # 处理路径参数
    if file_path is not None:
        from urllib.parse import unquote
        decoded_path = unquote(file_path)
        current_path = os.path.normpath(os.path.join(base_path, decoded_path))
        
        # 防止路径越界
        if not os.path.abspath(current_path).startswith(os.path.abspath(base_path)):
            current_path = base_path
    else:
        current_path = base_path  # 根路径直接使用基础路径
    
    # 如果是文件路径，直接返回文件内容
    if os.path.isfile(current_path):
        return serve_file(request, file_path if file_path else '')

    # 移除查询参数处理，完全使用路径参数

    # 获取分页和排序参数
    limit = int(request.GET.get('limit', 10))
    page = int(request.GET.get('page', 1))
    offset = (page - 1) * limit
    sort = request.GET.get('sort', 'name')  # 默认按名称排序
    order = request.GET.get('order', 'asc')  # 默认升序

    # 获取文件列表 (默认启用递归搜索)
    recursive = request.GET.get('recursive', 'true').lower() == 'true'
    search_result = list_files(
        current_path, 
        request.GET.get('q'), 
        recursive,
        limit=limit,
        offset=offset,
        sort=sort,
        order=order
    )
    
    # 转换文件路径为相对路径
    for file in search_result['results']:
        if file['is_dir']:
            file['path'] = os.path.relpath(file['path'], settings.FILE_ROOT).replace('\\', '/')
    
    # 分页处理
    paginator = Paginator(range(search_result['total']), limit)
    page_obj = paginator.get_page(page)
    
    # 生成面包屑导航数据
    breadcrumbs = []
    rel_path = os.path.relpath(current_path, base_path)
    
    if rel_path != '.':
        parts = rel_path.split(os.sep)
        accumulated_path = base_path
        for i, part in enumerate(parts):
            # 构建完整路径
            accumulated_path = os.path.join(accumulated_path, part)
            # 获取相对于base_path的路径并处理中文编码
            rel_to_base = os.path.relpath(accumulated_path, base_path)
            from urllib.parse import quote
            # 确保路径不以斜杠开头并处理中文路径
            rel_to_base = rel_to_base.replace(os.sep, '/').lstrip('/')
            breadcrumbs.append({
                'name': part,
                'url': f"/{quote(rel_to_base)}"
            })
    
    response = render(request, 'file_manager/index.html', {
        'files': search_result['results'],
        'page_obj': page_obj,
        'current_path': current_path,
        'root_path': base_path,
        'breadcrumbs': breadcrumbs,
        'sort': sort,
        'order': order
    })
    
    # 设置缓存
    cache.set(cache_key, response, settings.FILE_LIST_CACHE_TIMEOUT)
    
    return response

from django.http import HttpRequest, HttpResponse
from typing import Optional

def preview_file(request: HttpRequest, file_path: str) -> HttpResponse:
    """
    文件预览视图 - 支持多种文件类型的预览
    
    Args:
        request: HttpRequest对象
        file_path: 要预览的文件路径(相对FILE_ROOT)
            
    Returns:
        HttpResponse: 渲染的预览页面，根据文件类型返回不同模板:
            - 图片(.png/.jpg/.jpeg/.gif/.bmp): preview_image.html
            - 视频(.mp4/.webm/.ogg): preview_video.html
            - 音频(.mp3/.wav/.ogg): preview_audio.html
            - PDF(.pdf): preview_pdf.html
            - Office文档(.doc/.docx/.xls/.xlsx/.ppt/.pptx): preview_office.html
        HttpResponse: 错误响应(400/404/500)
        
    Raises:
        Http400: 当文件路径无效时
        Http404: 当文件不存在时
        Http500: 当服务器内部错误时
        
    Example:
        >>> 请求: GET /preview/path/to/image.jpg
        <<< 响应: 图片预览页面或错误响应
    """
    import logging
    logging.warning(f"preview_file: file_path={file_path!r}, type={type(file_path)}")
    # 视图层保险处理，file_path必须为非空字符串
    if not file_path or not isinstance(file_path, str) or file_path.strip() == '':
        return HttpResponse(f"file_path无效，无法预览，file_path={file_path!r}, type={type(file_path)}", status=400)
    try:
        from urllib.parse import unquote
        decoded_path = unquote(file_path)
        full_path = os.path.normpath(os.path.join(settings.FILE_ROOT, decoded_path))
        if not full_path.startswith(os.path.normpath(settings.FILE_ROOT)):
            return HttpResponse("无效的文件路径", status=400)
        if not os.path.exists(full_path) or os.path.isdir(full_path):
            return HttpResponse("文件不存在", status=404)
        # 保证file_path为非空字符串且无首尾斜杠
        if file_path and isinstance(file_path, str):
            file_path = file_path.strip("/")
        else:
            file_path = ""
    except Exception as e:
        return HttpResponse("服务器内部错误", status=500)
    # 获取文件扩展名，防止None异常
    ext = os.path.splitext(full_path)[1]
    if ext:
        ext = ext.lower()
    else:
        ext = ''
    if ext in ('.png', '.jpg', '.jpeg', '.gif', '.bmp'):
        return render(request, 'file_manager/preview_image.html', {
            'file_path': file_path,
            'file_name': os.path.basename(file_path)
        })
    elif ext in ('.mp4', '.webm', '.ogg'):
        return render(request, 'file_manager/preview_video.html', {
            'file_path': file_path,
            'file_name': os.path.basename(file_path)
        })
    elif ext in ('.mp3', '.wav', '.ogg'):
        return render(request, 'file_manager/preview_audio.html', {
            'file_path': file_path,
            'file_name': os.path.basename(file_path)
        })
    elif ext == '.pdf':
        return render(request, 'file_manager/preview_pdf.html', {
            'file_path': file_path,
            'file_name': os.path.basename(file_path)
        })
    elif ext in ('.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx'):
        return render(request, 'file_manager/preview_office.html', {
            'file_path': file_path,
            'file_name': os.path.basename(file_path)
        })
    elif ext in ['.xls', '.xlsx']:
        try:
            import pandas as pd
            df = pd.read_excel(full_path)
            html_table = df.to_html(index=False, classes="table table-bordered table-sm")
        except Exception as e:
            html_table = f'<div class="alert alert-danger">Excel 解析失败: {e}</div>'
        return render(request, 'file_manager/preview_excel.html', {
            'file_path': file_path,
            'html_table': html_table,
        })
    return HttpResponse("Preview not supported for this file type", status=400)

def serve_pdf(request, file_path):
    """
    PDF文件服务视图
    
    Args:
        request: HttpRequest对象
        file_path: 要预览的PDF文件路径(相对FILE_ROOT)
            
    Returns:
        FileResponse: PDF文件内容
        HttpResponse: 错误响应
        
    Raises:
        400: 如果文件路径无效
        404: 如果文件不存在
        500: 如果发生服务器内部错误
    """
    try:
        # 处理URL编码的路径
        from urllib.parse import unquote
        decoded_path = unquote(file_path)
        
        # 移除可能的FILE_ROOT前缀和多余斜杠
        decoded_path = decoded_path.replace('\\', '/').strip('/')
        file_root = settings.FILE_ROOT.replace('\\', '/').rstrip('/')
        if decoded_path.startswith(file_root):
            decoded_path = decoded_path[len(file_root):].lstrip('/')
        
        # 拼接路径并规范化
        full_path = os.path.normpath(os.path.join(settings.FILE_ROOT, decoded_path))
        
        # 安全校验
        if not full_path.startswith(os.path.normpath(settings.FILE_ROOT)):
            return HttpResponse("无效的文件路径", status=400)
            
        if not os.path.exists(full_path) or os.path.isdir(full_path):
            return HttpResponse("文件不存在", status=404)
            
        # 设置PDF内容类型
        # 处理中文文件名
        filename = os.path.basename(full_path)
        quoted_filename = quote(filename.encode('utf-8'))
        
        response = FileResponse(open(full_path, 'rb'))
        response['Content-Type'] = 'application/pdf'
        response['Content-Disposition'] = f'inline; filename*=UTF-8\'\'{quoted_filename}'
        return response
    except Exception as e:
        return HttpResponse("服务器内部错误", status=500)

def serve_resource(request, file_path):
    """
    统一资源服务视图
    
    Args:
        request: HttpRequest对象
        file_path: 要访问的资源路径(相对FILE_ROOT)
            
    Returns:
        HttpResponse: 文件下载/预览或目录浏览页面
    """
    try:
        import logging
        logger = logging.getLogger(__name__)
        logger.info(f"原始file_path: {file_path}")
        
        # 处理URL编码的路径
        from urllib.parse import unquote
        decoded_path = unquote(file_path)
        logger.info(f"解码后路径: {decoded_path}")
        
        # 规范化路径
        decoded_path = decoded_path.replace('\\', '/').strip('/')
        logger.info(f"规范化路径: {decoded_path}")
        
        # 拼接完整路径
        full_path = os.path.normpath(os.path.join(settings.FILE_ROOT, decoded_path))
        logger.info(f"完整文件路径: {full_path}")
        
        # 安全校验
        if not os.path.abspath(full_path).startswith(os.path.abspath(settings.FILE_ROOT)):
            logger.warning(f"路径越界: {full_path}")
            return HttpResponse("无效的资源路径", status=400)
            
        if not os.path.exists(full_path):
            logger.warning(f"文件不存在: {full_path}")
            return HttpResponse("资源不存在", status=404)
        
        # 如果是目录，返回目录浏览页面
        if os.path.isdir(full_path):
            logger.info("返回目录浏览")
            return index(request, file_path)
        
        # 如果是文件，根据请求参数决定是预览还是下载
        if request.GET.get('preview') == 'true':
            logger.info("返回预览页面")
            return preview_file(request, file_path)
        else:
            logger.info("返回文件下载")
            # 处理中文文件名
            filename = os.path.basename(full_path)
            quoted_filename = quote(filename.encode('utf-8'))
            
            response = FileResponse(open(full_path, 'rb'))
            response['Content-Disposition'] = f'attachment; filename*=UTF-8\'\'{quoted_filename}'
            return response
    except Exception as e:
        import logging
        logging.error(f"处理资源请求时出错: {str(e)}", exc_info=True)
        return HttpResponse("服务器内部错误", status=500)

def serve_file(request, file_path):
    """
    统一文件服务视图
    
    Args:
        request: HttpRequest对象
        file_path: 要访问的文件路径(相对FILE_ROOT)
            
    Returns:
        FileResponse: 文件下载响应
        HttpResponse: 渲染的预览页面或错误响应
        
    Raises:
        400: 如果文件路径无效
        404: 如果文件不存在
    """
    try:
        import logging
        logging.info(f"处理文件请求: {file_path}")
        
        # 处理URL编码的路径
        from urllib.parse import unquote
        decoded_path = unquote(file_path)
        
        # 拼接路径并规范化
        full_path = os.path.normpath(os.path.join(settings.FILE_ROOT, decoded_path))
        logging.info(f"完整文件路径: {full_path}")
        
        # 安全校验
        if not full_path.startswith(os.path.normpath(settings.FILE_ROOT)):
            logging.warning(f"无效的文件路径: {full_path}")
            return HttpResponse("无效的文件路径", status=400)
            
        if not os.path.exists(full_path) or os.path.isdir(full_path):
            logging.warning(f"文件不存在: {full_path}")
            return HttpResponse("文件不存在", status=404)
            
        # 获取文件类型
        ext = os.path.splitext(full_path)[1].lower()
        logging.info(f"文件类型: {ext}")
        
        # 如果是图片、视频、音频、PDF或Office文档，尝试预览
        if ext in ('.png', '.jpg', '.jpeg', '.gif', '.bmp', '.mp4', '.webm', '.ogg', 
                  '.mp3', '.wav', '.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx'):
            logging.info("返回预览页面")
            return preview_file(request, file_path)
            
        # 其他文件直接下载
        logging.info("返回文件下载")
        # 处理中文文件名
        filename = os.path.basename(full_path)
        quoted_filename = quote(filename.encode('utf-8'))
        
        response = FileResponse(open(full_path, 'rb'))
        response['Content-Disposition'] = f'attachment; filename*=UTF-8\'\'{quoted_filename}'
        return response
    except Exception as e:
        return HttpResponse("服务器内部错误", status=500)

def download_file(request, file_path):
    """
    文件下载/在线播放视图，支持HTTP Range分块请求，提升大文件在线播放体验。
    支持多用户同时播放同一视频。
    """
    import logging
    from urllib.parse import unquote, quote
    import os
    import mimetypes
    from django.http import StreamingHttpResponse, FileResponse, HttpResponse
    logger = logging.getLogger(__name__)
    try:
        logger.info(f"开始处理下载请求，原始路径: {file_path}")
        decoded_path = unquote(file_path)
        logger.info(f"解码后路径: {decoded_path}")
        decoded_path = decoded_path.replace('\\', '/').strip('/')
        file_root = settings.FILE_ROOT.replace('\\', '/').rstrip('/')
        if decoded_path.startswith(file_root):
            decoded_path = decoded_path[len(file_root):].lstrip('/')
        full_path = os.path.normpath(os.path.join(settings.FILE_ROOT, decoded_path))
        logger.info(f"完整文件路径: {full_path}")
        if not full_path.startswith(os.path.normpath(settings.FILE_ROOT)):
            logger.warning(f"路径越界: {full_path}")
            return HttpResponse("无效的文件路径", status=400)
        if not os.path.exists(full_path) or os.path.isdir(full_path):
            logger.warning(f"文件不存在: {full_path}")
            return HttpResponse("文件不存在", status=404)
        file_size = os.path.getsize(full_path)
        filename = os.path.basename(full_path)
        quoted_filename = quote(filename.encode('utf-8'))
        # 自动识别Content-Type
        content_type, _ = mimetypes.guess_type(full_path)
        if not content_type:
            content_type = 'application/octet-stream'
        range_header = request.META.get('HTTP_RANGE', '').strip()
        if range_header:
            import re
            range_match = re.match(r'bytes=(\d+)-(\d*)', range_header)
            if range_match:
                start_str = range_match.group(1)
                end_str = range_match.group(2)
                start = int(start_str) if start_str else 0
                end = int(end_str) if end_str else file_size - 1
                if end >= file_size:
                    end = file_size - 1
                length = end - start + 1
                def file_iterator(fp, offset, length, chunk_size=8192):
                    fp.seek(offset)
                    remaining = length
                    while remaining > 0:
                        chunk = fp.read(min(chunk_size, remaining))
                        if not chunk:
                            break
                        yield chunk
                        remaining -= len(chunk)
                response = StreamingHttpResponse(
                    file_iterator(open(full_path, 'rb'), start, length),
                    status=206
                )
                response['Content-Type'] = content_type
                response['Content-Length'] = str(length)
                response['Content-Range'] = f'bytes {start}-{end}/{file_size}'
                response['Accept-Ranges'] = 'bytes'
                ext = os.path.splitext(full_path)[1].lower()
                if ext in ('.mp4', '.webm', '.ogg', '.mp3', '.wav'):
                    response['Content-Disposition'] = f'inline; filename*=UTF-8\'\'{quoted_filename}'
                else:
                    response['Content-Disposition'] = f'attachment; filename*=UTF-8\'\'{quoted_filename}'
                return response
        # 非Range请求，普通下载
        response = FileResponse(open(full_path, 'rb'))
        response['Content-Disposition'] = f'attachment; filename*=UTF-8\'\'{quoted_filename}'
        response['Content-Length'] = str(file_size)
        response['Accept-Ranges'] = 'bytes'
        response['Content-Type'] = content_type
        return response
    except FileNotFoundError:
        logger.error(f"文件不存在: {full_path}")
        return HttpResponse("文件不存在", status=404)
    except PermissionError:
        logger.error(f"无权限访问文件: {full_path}")
        return HttpResponse("无权限访问文件", status=403)
    except Exception as e:
        logger.error(f"文件下载错误 - 路径: {full_path}, 错误: {str(e)}", exc_info=True)
        return HttpResponse(f"服务器内部错误: {str(e)}", status=500)

def serve_media(request, rel_path):
    """
    简洁URL媒体文件服务视图
    
    Args:
        request: HttpRequest对象
        rel_path: 相对路径(如"视频/前世的脸-刘妍.mp4")
            
    Returns:
        FileResponse: 文件响应
        HttpResponse: 错误响应
        
    Raises:
        404: 如果文件不存在
        403: 如果路径越界
    """
    try:
        # 将相对路径转换为绝对路径
        full_path = os.path.normpath(os.path.join(settings.FILE_ROOT, rel_path))
        
        # 安全检查：确保路径在FILE_ROOT下
        if not os.path.abspath(full_path).startswith(os.path.abspath(settings.FILE_ROOT)):
            return HttpResponse('路径越界', status=403)
            
        # 检查文件是否存在
        if not os.path.isfile(full_path):
            return HttpResponse('文件不存在', status=404)
            
        # 使用现有的serve_file逻辑
        return serve_file(request, rel_path)
    except Exception as e:
        import logging
        logging.getLogger(__name__).error(f"文件服务错误: {str(e)}", exc_info=True)
        return HttpResponse('服务器内部错误', status=500)